/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport, MatcherCallback, MatcherTypes, Rule, ViewTreeNodeMatcher } from '../../Index';
import { Stmt, ViewTreeNode } from 'arkanalyzer';

const gMetaData: BaseMetaData = {
    severity: 3,
    ruleDocPath: 'docs/foreach-nested-in-lazy-foreach-check.md',
    description: 'Avoid nesting Foreach inside the ListItem subcomponent of LazyForeach.'
};

export class ForeachNestedInLazyForeachCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private vtnMatcher: ViewTreeNodeMatcher = {
        matcherType: MatcherTypes.VIEW_TREE_NODE,
        match(node: ViewTreeNode): boolean {
            return node.name === 'LazyForEach';
        }
    };

    public registerMatchers(): MatcherCallback[] {
        const matchMethodCb: MatcherCallback = {
            matcher: this.vtnMatcher,
            callback: this.check
        };
        return [matchMethodCb];
    }

    public check = (node: ViewTreeNode): void => {
        const listItem = node.children.find(sub => sub.name === 'ListItem');
        if (!listItem) {
            return;
        }
        for (const item of listItem.children) {
            const foreach = this.findForEach(item);
            if (foreach) {
                const stmt = foreach.attributes.get('create')?.[0];
                if (stmt) {
                    this.reportIssue(stmt, foreach.name.length);
                }
            }
        }
    };

    private findForEach(node: ViewTreeNode, visited: Set<ViewTreeNode> = new Set()): ViewTreeNode | undefined {
        if (visited.has(node)) {
            return undefined;
        } else {
            visited.add(node);
        }
        if (node.name === 'ForEach') {
            return node;
        }
        for (const child of node.children) {
            const foreach = this.findForEach(child, visited);
            if (foreach) {
                return foreach;
            }
        }
        return undefined;
    }


    private reportIssue(stmt: Stmt, length: number): void {
        const filePath = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile().getFilePath();
        let fullPosition = stmt.getOperandOriginalPosition(0);
        if (!fullPosition) {
            return;
        }
        let lineNum = fullPosition.getFirstLine();
        let startColumn = fullPosition.getFirstCol();
        let endColumn = startColumn + length;
        if (lineNum === -1 || startColumn === -1) {
            return;
        }
        const severity = this.rule.alert ?? this.metaData.severity;
        let defects = new Defects(lineNum, startColumn, endColumn, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
    }
}